package be.belgium.eid.security;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Vector;

import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.ocsp.BasicOCSPResp;
import org.bouncycastle.ocsp.CertificateID;
import org.bouncycastle.ocsp.OCSPException;
import org.bouncycastle.ocsp.OCSPReq;
import org.bouncycastle.ocsp.OCSPReqGenerator;
import org.bouncycastle.ocsp.OCSPResp;
import org.bouncycastle.ocsp.SingleResp;

/**
 * The OCSP client uses the Bouncy castle API to perform a single OCSP request.
 * The client connects with the given host address on the http port to perform a
 * post request with the OCSP request in it. The response is then parsed and the
 * certificate status is updated, after which a reply is generated.
 * 
 * @author Kristof Overdulve
 * @version 1.0.0 21 Apr 2008
 */
public class OCSPClient {
	public static OCSPReq generateOCSPRequest(final X509Certificate issuerCert,
			final BigInteger serialNumber) throws OCSPException {
		// Add provider BC
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

		// Generate the id for the certificate we are looking for and generate the request
		final OCSPReqGenerator gen = new OCSPReqGenerator();
		gen.addRequest(new CertificateID(CertificateID.HASH_SHA1,
				issuerCert, serialNumber));

		// create details for nonce extension
		final BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
		final Vector<DERObjectIdentifier> oids = new Vector<DERObjectIdentifier>();
		final Vector<X509Extension> values = new Vector<X509Extension>();

		oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
		values.add(new X509Extension(false, new DEROctetString(nonce
				.toByteArray())));

		// Set the OID extensions
		gen.setRequestExtensions(new X509Extensions(oids, values));

		// Return result
		return gen.generate();
	}

	/**
	 * Sends a OCSP request to the given host address and processes the reply.
	 * The status of the cert is then adjusted to reflect the results in the
	 * response generated by the OCSP responder.
	 * 
	 * @param issuer
	 *            is the certificate of the Certificate Authority issuing the
	 *            given cert
	 * @param cert
	 *            is the certificate to verify, using it's serial key and the
	 *            certificate of the issuer, the status of this parameter is
	 *            modified according to the results of the verification
	 * @param hostaddr
	 *            is the host address (HTTP capable) to send the request to
	 * @return whether the verification succeeded
	 * @throws OCSPException
	 *             when the OCSP request couldn't be generated
	 * @throws IOException
	 *             when the encoding of the request failed
	 * @throws CertificateException
	 *             when the x509 certificate couldn't be retrieved from the
	 *             given certificates
	 */
	public static boolean processOCSPRequest(final Certificate issuer,
			final Certificate cert, final String hostaddr)
			throws OCSPException, IOException, CertificateException {
		// Generate OCSP request
		final OCSPReq request = generateOCSPRequest(
				issuer.getX509Certificate(), cert.getX509Certificate()
						.getSerialNumber());
		final byte[] array = request.getEncoded();

		HttpURLConnection con = null;
		final URL url = new URL((String) hostaddr);
		con = (HttpURLConnection) url.openConnection();
		con.setRequestProperty("Content-Type", "application/ocsp-request");
		con.setRequestProperty("Accept", "application/ocsp-response");
		con.setDoOutput(true);
		final OutputStream out = con.getOutputStream();
		final DataOutputStream dataOut = new DataOutputStream(
				new BufferedOutputStream(out));
		dataOut.write(array);

		dataOut.flush();
		dataOut.close();

		// Get Response
		final InputStream in = (InputStream) con.getContent();

		// Check errors in response:
		if (con.getResponseCode() / 100 != 2) {
			return false;
		}

		// Fetch the responses
		final OCSPResp ocspResponse = new OCSPResp(in);
		final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse
				.getResponseObject();

		if (basicResponse == null) {
			return false;
		} else {
			final SingleResp[] responses = basicResponse.getResponses();
			final SingleResp resp = responses[0];
			final Object status = resp.getCertStatus();

			// Check the status of the response and adjust certificate
			if (status instanceof org.bouncycastle.ocsp.RevokedStatus) {
				cert.setStatus(CertificateStatus.BEID_CERTSTATUS_CERT_REVOKED);
				return false;
			} else if (status instanceof org.bouncycastle.ocsp.UnknownStatus) {
				cert.setStatus(CertificateStatus.BEID_CERTSTATUS_CERT_UNKNOWN);
				return false;
			} else {
				if (cert.getStatus().equals(
						CertificateStatus.BEID_CERTSTATUS_CERT_NOT_VALIDATED)) {
					cert.setStatus(CertificateStatus.BEID_CERTSTATUS_CERT_VALIDATED_OK);
				}
				return true;
			}
		}
	}
}
